/**
 * OpenLayers 3 Layer Switcher Control.
 * 
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.ControlOptions}
 *            options Control options.
 */
ol.control.LayerSwitcher = function(opt_options) {
	var options = opt_options || {};
	this.mapListeners = [];
	this.hiddenClassName = 'ol-unselectable ol-control layer-switcher';
	this.shownClassName = this.hiddenClassName + ' shown';
	var element = document.createElement('div');
	element.className = this.hiddenClassName;
	var button = document.createElement('button');
	element.appendChild(button);
	this.panel = document.createElement('div');
	this.panel.className = 'panel';
	element.appendChild(this.panel);
	var this_ = this;
	element.onmouseover = function(e) {
		this_.showPanel();
	};
	button.onclick = function(e) {
		this_.showPanel();
	};
	element.onmouseout = function(e) {
		e = e || window.event;
		if (!element.contains(e.toElement)) {
			this_.hidePanel();
		}
	};
	ol.control.Control.call(this, {
		element : element,
		target : options.target
	});
};
ol.inherits(ol.control.LayerSwitcher, ol.control.Control);
/**
 * Show the layer panel
 */
ol.control.LayerSwitcher.prototype.showPanel = function() {
	if (this.element.className != this.shownClassName) {
		this.element.className = this.shownClassName;
		this.render();
	}
};
/**
 * Hide the layer panel
 */
ol.control.LayerSwitcher.prototype.hidePanel = function() {
	if (this.element.className != this.hiddenClassName) {
		this.element.className = this.hiddenClassName;
	}
};
/**
 * Cause the panel to be re-draw to represent the current layer state.
 */
ol.control.LayerSwitcher.prototype.render = function() {
	while (this.panel.firstChild) {
		this.panel.removeChild(this.panel.firstChild);
	}
	var ul = document.createElement('ul');
	this.panel.appendChild(ul);
	this.renderLayers_(this.getMap(), ul);
};
/**
 * Set the map instance the control is associated with.
 * 
 * @param {ol.Map}
 *            map The map instance.
 */
ol.control.LayerSwitcher.prototype.setMap = function(map) {
	// Clean up listeners associated with the previous map
	for ( var i = 0, key; i < this.mapListeners.length; i++) {
		this.getMap().unByKey(this.mapListeners[i]);
	}
	this.mapListeners.length = 0;
	// Wire up listeners etc. and store reference to new map
	ol.control.Control.prototype.setMap.call(this, map);
	var this_ = this;
	this.mapListeners.push(map.on('pointerdown', function() {
		this_.hidePanel();
	}));
	this.render();
};
/**
 * Toggle the visible state of a layer. Takes care of hiding other layers in the
 * same exclusive group if the layer is toggle to visible.
 * 
 * @private
 * @param {ol.layer.Base}
 *            The layer whos visibility will be toggled.
 */
ol.control.LayerSwitcher.prototype.toggleLayer_ = function(lyr) {
	var map = this.getMap();
	lyr.setVisible(!lyr.getVisible());
	if (lyr.get('type') === 'base') {
		// Hide all other base layers regardless of grouping
		ol.control.LayerSwitcher.forEachRecursive(map, function(l, idx, a) {
			if (l.get('type') === 'base' && l != lyr) {
				l.setVisible(false);
			}
		});
	}
};
/**
 * Render all layers that are children of a group.
 * 
 * @private
 * @param {ol.layer.Base}
 *            lyr Layer to be rendered (should have a title property).
 * @param {Number}
 *            idx Position in parent group list.
 */
ol.control.LayerSwitcher.prototype.renderLayer_ = function(lyr, idx) {
	var this_ = this;
	var li = document.createElement('li');
	var lyrTitle = lyr.get('title');
	var lyrId = lyr.get('title').replace(' ', '-') + '_' + idx;
	var label = document.createElement('label');
	if (lyr.getLayers) {
		li.className = 'group';
		label.innerHTML = lyrTitle;
		li.appendChild(label);
		var ul = document.createElement('ul');
		li.appendChild(ul);
		this.renderLayers_(lyr, ul);
	} else {
		var input = document.createElement('input');
		if (lyr.get('type') === 'base') {
			input.type = 'radio';
			input.name = 'base';
		} else {
			input.type = 'checkbox';
		}
		input.id = lyrId;
		input.checked = lyr.get('visible');
		input.onchange = function() {
			this_.toggleLayer_(lyr);
		};
		li.appendChild(input);
		label.htmlFor = lyrId;
		label.innerHTML = lyrTitle;
		li.appendChild(label);
	}
	return li;
};
/**
 * Render all layers that are children of a group.
 * 
 * @private
 * @param {ol.layer.Group}
 *            lyr Group layer whos children will be rendered.
 * @param {Element}
 *            elm DOM element that children will be appended to.
 */
ol.control.LayerSwitcher.prototype.renderLayers_ = function(lyr, elm) {
	var lyrs = lyr.getLayers().getArray().slice().reverse();
	for ( var i = 0, l; i < lyrs.length; i++) {
		l = lyrs[i];
		if (l.get('title')) {
			elm.appendChild(this.renderLayer_(l, i));
		}
	}
};
/**
 * Call the supplied function for each layer in the passed layer group recursing
 * nested groups.
 * 
 * @param {ol.layer.Group}
 *            lyr The layer group to start iterating from.
 * @param {Function}
 *            fn Callback which will be called for each ol.layer.Base found
 *            under lyr. The signature for fn is the same as
 *            ol.Collection#forEach
 */
ol.control.LayerSwitcher.forEachRecursive = function(lyr, fn) {
	lyr.getLayers().forEach(function(lyr, idx, a) {
		fn(lyr, idx, a);
		if (lyr.getLayers) {
			ol.control.LayerSwitcher.forEachRecursive(lyr, fn);
		}
	});
};